package an_lexico;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Parse an source file to find its basic tokens
 * 
 * @author Fernando Governatore, Pedro Sol
 */
public class AnLexico {

    /**
     * Maximum number of characters accepted in the Token.
     */
    private static final int MAX_TOKEN_SIZE = 50;
    /**
     * State Machine Initial State. After the token is returned, the machine
     * come back to this state.
     */
    private int initialState = 0;
    /**
     * Number of lines and collumns of the transition table.
     */
    private int col,  lin;
    /**
     * If the end of file was reached it is set to True. Else its false.
     */
    private boolean eof;
    /**
     * The ID of the token correponding to the state. Eache line of this vector
     * corresponds to the same line in the transition table.
     */
    private String finalStateID[ /* estado */];
    /**
     * If the value of the line is bigger than zero, then the state is a final 
     * state. The number stored in the line is the number associated with the 
     * token(Token Type).
     * Eache line of this vector corresponds to the same line in the transition
     * table.
     */
    private int finalState[ /* estado */];
    /**
     * Last number associated to one state of the machine.
     * The reserved words number(Token Type) will always be bigger than this
     * number.
     */
    private int lastFinalStateTokenType;
    /**
     * Source file to be analized.
     */
    private InputStreamReader sourceFile;
    /**
     * Specifies wich characters will be associated with eache collumn of the 
     * transition table.
     * eg.:
     * if inputMap[3][0] = 'a' and inputMap[3][1] = 'z' then
     * if any character between 'a' and 'z' is read from the source file, the 
     * transition in the collumn number 3 will be used.
     */
    private char inputMap[][];
    /**
     * Stores the last char read from the source file, and not included in an 
     * Token, after the getToken returns.
     */
    private char lastChar;
    /**
     * Current state of the machine state.
     */
    private int state;
    /**
     * Transition table of the machine state. 
     * transitions[ inputMap ][ state ]
     */
    private int transitions[][];
    /**
     * Stores the reserved words that are not recognized directly by the state
     * machine.
     * Before the getToken method returns, it search this vector for reserved
     * words that maches the input.
     */
    private String reservedWords[];
    /**
     * If a token should be returned to AnLexico, it is stored here.
     * Then it is used in the next call to getToken.
     * Could be implemented with an Stack, but only one position should be used 
     * anyway.
     */
    private Token rejectedToken; //If a token is not consumed in AnSintatico, then it comes back here;

    /**
     * Initializes only the most basic things that should be initialized for any
     * language/state machine.
     */
    protected AnLexico() {
        this.lastChar = '\0';
        this.eof = false;
        rejectedToken = null;
    }

    /**
     * Set the last number given for an final state(Last Token Type) so that the
     * reserved words can be numbered after then.
     * @param lastFinalStateTokenType
     */
    protected void setLastFinalStateTokenType(int lastFinalStateTokenType) {
        this.lastFinalStateTokenType = lastFinalStateTokenType;
    }

    /**
     * Set the reserved words of the machine.
     * @param reservedWords
     */
    protected void setReservedWords(String[] reservedWords) {
        this.reservedWords = reservedWords;
    }

    /**
     * Get the ID of the specified token type.
     * @param tokenType
     * @return ID
     */
    public String getTokenID(int tokenType) {
        if (tokenType < lastFinalStateTokenType) {
            return finalStateID[tokenType];
        } else {
            return reservedWords[tokenType - lastFinalStateTokenType - 2];
        }
    }

    /**
     * Get the Type(number) of the token based in the suplied ID.
     * @param id
     * @return the number corresponding to the id.
     */
    public int getTokenTypeByID(String id) {
        int i = 0;
        int type = 0;
        while (i < finalStateID.length) {
            if (id.equals(finalStateID[i])) {
                return i;
            }
            if(finalStateID[i] != null){
                type++;
            }
            i++;
        }
        //If it has come this far, the ID should be an reserved word
        type = lastFinalStateTokenType;
        int r = 0;
        while (r < reservedWords.length) {
            if (id.equals(reservedWords[r])) {
                return type + r +2;
            }
            r++;
        }
        throw new Error("Token \"" + id + "\" Not Found");
    }

    /**
     * Set the number of collumns of the tables: inputMap, transitionTable
     * @param col Number of collumns of the tables: inputMap, transitionTable
     */
    protected void setCol(int col) {
        this.col = col;
    }

    /**
     * Set which states are final in the machine. If an state is not final, then
     * its number must be -1. If it is final, its number is the type of token to
     * be returned.
     * @param finalState Estados Finais
     */
    protected void setFinalState(int[] finalState) {
        this.finalState = finalState;
    }

    /**
     *
     * @param finalStateID ID dos Estados Finais
     */
    protected void setTokenTypeID(String[] finalStateID) {
        this.finalStateID = finalStateID;
    }

    /**
     * Set the source file to be parsed by the lexical state machine.
     * @param fonte source file of the program to be compiled.
     */
    protected void setSourceFile(InputStreamReader fonte) {
        this.sourceFile = fonte;
    }

    /**
     * Set the initial state of the state machine
     * @param initialState
     */
    protected void setInitialState(int initialState) {
        this.initialState = initialState;
    }

    /**
     *
     * @param inputMap Tabela de inputs aceitos pela maquina de estados
     */
    protected void setInputMap(char[][] inputMap) {
        this.inputMap = inputMap;
    }

    /**
     *
     * @param lin Numero de linhas da tabela transitionTable e finalState
     */
    protected void setLin(int lin) {
        this.lin = lin;
    }

    /**Find which transition must be done by the machine.
     *
     * @param transitionTable Transicoes feitas pela maquina de estados, dependente do inputMap
     */
    protected void setTransitions(int[][] transitionTable) {
        this.transitions = transitionTable;
    }

    /**
     * Prints the transition table of AnLexico
     */
    public void printLex() {
        System.out.print("  ");

        for (int i = 0; i < col; i++) {
            System.out.print(inputMap[i][0] + "-" + inputMap[i][1] + "  ");
        }

        System.out.print("\n");

        for (int i = 0; i < lin; i++) {
            System.out.print((finalState[i] > 0)
                    ? "f "
                    : "n");

            for (int j = 0; j < col; j++) {
                System.out.print("  " + transitions[j][i] + " ");
            }

            System.out.print("\n");
        }
    }

    /**
     * Get Character from file and make the state machine transition. When there
     * are no transitions left, if the machine is in an final state, returns the
     * token.
     * @return t - Token
     */
    public Token getToken() {
        char[] tokenID = new char[MAX_TOKEN_SIZE];
        Token token = new Token();

        if (rejectedToken != null) {
            Token t = rejectedToken;
            rejectedToken = null;
            return t;
        }

        try {
            int position = 0;

            state = initialState;

            /**
             * Main loop of the state machine
             *
             *     Get the next input for the state machine
             *
             *     Find the transition collumn. The state is the line.
             *
             *     if there is a possible transition
             *         does the transition
             *     else if the state is a final state
             *         returns the token
             *     else
             *         returns an error
             */
            while (!eof) {
                int input = getChar();
                int i = findTransitionCollumn(input);

                if (transitions[i][state] == -1) {    // End of Token/Error
                    if (finalState[state] > 0) {      // End
                        tokenID[position] = '\0';

                        /** Mantain the last character read(the one that was not used) */
                        lastChar = (char) input;

                        /** Define Type of Token */
                        token.setTokenType(finalState[state]);
                        token.setID(tokenID);
                        int reserved = getReservedWordTokenType(token.getID());
                        if (reserved > -1) {
                            token.setTokenType(reserved);
                        }

                        return token;
                    } else {    // Error
                        throw new Error("Invalid Token.");
                    }
                } else {
                    tokenID[position] = (char) input;
                    state = transitions[i][state];

                    if (tokenID[position] != ' ') {
                        position++;
                    } else {
                        tokenID[position] = '\0';
                    }

                    if (position >= MAX_TOKEN_SIZE) {
                        throw new Error("Token muito grande.");
                    }
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(AnLexico.class.getName()).log(Level.SEVERE, null, ex);
        }

        if (eof) {
            token.setID(tokenID);
            token.setTokenType(Token.Type_EOF);
        }

        return token;
    }

    /**
     * Set the next Token to be returned as the token in the parameter.
     * It should be used to give back an token that is not consumed.
     * @param t
     */
    public void setNextToken(Token t) {
        rejectedToken = t;
    }

    /**
     * Return the collumn of the transition corresponding to the character b.
     * @param input - caracter to be consumed by the machine
     * @return transition to be done;
     * @throws Error if the character cannot be consumed by the machine
     */
    private int findTransitionCollumn(int b) {
        boolean found = false;
        int i = 0;

        while (!found) {
            if ((b >= inputMap[i][0]) && (b <= inputMap[i][1])) {
                found = true;
            } else {
                i++;

                if (i >= col) {
                    throw new Error("Caractere Invalido(Code):" + b);
                }
            }
        }

        return i;
    }

    /**
     * Get the next character that have to be treated by the state machine
     * @return input Char as Integer
     * @throws java.io.IOException
     */
    private int getChar() throws IOException {
        int input;
        // Character.isSpaceChar(input);
        if (lastChar != '\0') {
            // the last char read but not used(come from the last Token read)
            input = lastChar;
            lastChar = '\0';
        } else {
            input = sourceFile.read();    // Read the next character from the input file
        }
        if ((input == '\t') || (input == '\n')) {
            // Tab and enter = space
            input = ' ';
        } else if (input == -1) {
            // EOF = space but ends the loop
            input = ' ';
            eof = true;
        }
        return input;
    }

    /**
     * Procura por ID entre as palavras reservadas e, se encontrar, retorna o 
     * numero da palavra reservada. Se não encontrar retorna -1
     * @param ID - Palavra a ser procurada
     * @return Numero da palavra reservada.
     */
    private int getReservedWordTokenType(String ID) {
        try {
            return getTokenTypeByID(ID);
        } catch (Error e) {
            return -1;
        }
    }
    
    
}
